using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SPGE;
using Microsoft.Xna.Framework;

namespace SPGE
{
    public class ScrollableItemList : GameItemComposite, ITouchUpdatable
    {

        public virtual int SelectedItemIndex
        { 
            get { return (int)Math.Round(PositioningValue); }
            set { PositioningValue = value; }
        }

        public IGameItem SelcetedItem
        {
            get
            {
                if (Items.Any())
                    return GetAt(SelectedItemIndex);
                else
                    return null;
            }
        }

        /// <summary>
        /// This is used to set or get the position of the contained items.
        /// If this is set to an integer then the item with corresponding index will be in focus.
        /// If this is set to a fractal value, the focal point will be between the items with indexes 
        /// corresponding to the ceiling and floor value of the PositioningValue.
        /// </summary>
        public float PositioningValue
        { get; set; }

        /// <summary>
        /// This changes the scale factor, which deterimined how much does unfocused items shrink depending on 
        /// how far from focus they are. Setting this to 1 will result in all items to not scaling. Values below 1
        /// will result in scaling down the items, while valuew above 1 will scale up. Suggested values are 0.2f-0.25f
        /// </summary>
        public float ScaleFactor
        { get; set; }

        /// <summary>
        /// This changes the transparency factor, which deterimined how much does unfocused items become transparent
        /// depending on how far from focus they are. Setting this to 1 will result in all items remaining opaque. Values 
        /// below 1 will result in making items more transparent as closer as this value approaches zero. Suggested values
        /// are 0.8-0.9 depending on the background color.
        /// </summary>
        private float transFactor;
        public float TransparencyFactor
        {
            get { return transFactor; }
            set { transFactor = value < 0 ? 0 : value; }
        }

        private Vector2? centerPos = null;
        /// <summary>
        /// This is used to set or get the position which the item with focus will be displayed.
        /// </summary>
        public virtual Vector2 CenterPosition
        {
            get { return centerPos ?? Position + (Size / 2f); }
            set { centerPos = value; }
        }

        /// <summary>
        /// This is used to give the illusion of momentum on the list items when scrolling.
        /// </summary>
        protected Vector2 PositioningValueSpeed
        { get; set; }
        /// <summary>
        /// This is used to give the illusion of momentum on the list items when scrolling.
        /// </summary>
        protected float PositioningValueSpeedDump
        { get; set; }

        /// <summary>
        /// Used to facilitate dragging operations.
        /// </summary>
        private bool DragReleased
        { get; set; }

        private Vector2 scrollAxis = Vector2.UnitX;
        /// <summary>
        /// This represents the axis on which the items will scroll on. This value is normalized automatically.
        /// </summary>
        public Vector2 ScrollingAxis
        {
            get { return scrollAxis; }
            set
            {
#if DEBUG
                if(value.Length() == 0)
                    throw new ArgumentException("Value cannot have 0 length");
#endif
                if (value.Length() != 0)
                    scrollAxis = value;

                scrollAxis.Normalize();
            }
        }

        public float Displacement
        { get; set; }


        public ScrollableItemList(Func<IEnumerable<IGameItem>> getItems, Vector2 position, Vector2 size)
            : base(getItems)
        {
            Displacement = 40f;

            Position = position;
            Size = size;
                
            RenderLevel = 0.8f;

            ScaleFactor = 0.2f;
            transFactor = 0.9f;

            PositioningValueSpeed = Vector2.Zero;
            PositioningValueSpeedDump = 0.7f;

            PositioningValue = 1;

            foreach (var item in Items)
                item.Origin = Vector2.One / 2f;
        }

        private bool touchCaughtInside = false, touchStarted = false;
        public void Update(Microsoft.Xna.Framework.GameTime gameTime, Microsoft.Xna.Framework.Input.Touch.TouchCollection collection, List<Microsoft.Xna.Framework.Input.Touch.GestureSample> gestures)
        {
            //Update the PositioningValue based on the actions of the player
            // If the player is performing a drag gesture we forcibly change the PositioningValue.
            // Else we move either slide the PositioningValue based on momentum or drag with minimum
            // velocity to the nearest integral value
            foreach (var gesture in gestures)
            {
                if (gesture.GestureType == Microsoft.Xna.Framework.Input.Touch.GestureType.None)
                    continue;

                if (!touchStarted)
                {
                    touchStarted = true;
                    touchCaughtInside = 
                        Position.X <= gesture.Position.X && Position.Y <= gesture.Position.Y && 
                        gesture.Position.X <= Position.X + Size.X && gesture.Position.Y <= Position.Y + Size.Y;
                }

                if(touchCaughtInside && gesture.GestureType == Microsoft.Xna.Framework.Input.Touch.GestureType.FreeDrag)
                {
                    PositioningValueSpeed += gesture.Delta;

                    DragReleased = false;
                }
                else if (touchCaughtInside && gesture.GestureType == Microsoft.Xna.Framework.Input.Touch.GestureType.Flick)
                {
                    PositioningValueSpeed = gesture.Delta;

                    DragReleased = true;
                }

                
                if (gesture.GestureType == Microsoft.Xna.Framework.Input.Touch.GestureType.DragComplete)
                {
                    DragReleased = true;
                    touchStarted = false;
                    touchCaughtInside = false;

                    if (PositioningValue <= -0.5f)
                    {
                        PositioningValueSpeed = Vector2.Zero;
                        PositioningValue = -0.5f;
                    }
                    else if (PositioningValue >= Items.Count() - 0.5f)
                    {
                        PositioningValueSpeed = Vector2.Zero;
                        PositioningValue = Items.Count() - 0.5f;
                    }
                }
            }

            //If we still have speed, we move a bit and reduce it.
            var roundTarget = (float)Math.Min(Math.Max(0f, Math.Round(PositioningValue)), Items.Count() - 1);
            if (PositioningValueSpeed.Length() >= 0.1)
            {
                var factor = (new Vector2(PositioningValueSpeed.X * ScrollingAxis.X, PositioningValueSpeed.Y * ScrollingAxis.Y)).Length();

                //With the dot product we can determine whether the speed is opposite of the scrolling axist
                // and hence a negative influence on the positioning value.
                factor *= Vector2.Dot(PositioningValueSpeed, ScrollingAxis) < 0 ? -1 : 1;

                PositioningValue -= factor / (3 * Displacement);

                PositioningValueSpeed *= PositioningValueSpeedDump;
            }
            else if (PositioningValue != roundTarget)
            {
                var side = PositioningValue > roundTarget ? -1f : 1f;
                if ((((ScrollingAxis.Length() * 2 * side) / Displacement) + PositioningValue - roundTarget)
                    * (PositioningValue - roundTarget) < 0)
                    // We overshot our target
                    PositioningValue = roundTarget;
                else
                    PositioningValue = ((ScrollingAxis.Length() * 2 * side) / Displacement) + PositioningValue;
            }

            PositioningValue = (float)Math.Min(Math.Max(-0.5f, PositioningValue), Items.Count() - 0.5f);

            //Update position based on PositioningValue
            // If we are on an item then leave it be.
            // If we are mid-way between items and not scrolling then slide towards the closer item.
            for (int i = 0; i < Items.Count(); i++)
            {
                GetAt(i).Position = CenterPosition - Position + (ScrollingAxis * Displacement * (i - PositioningValue));
                GetAt(i).RenderLevel = Math.Abs((i - PositioningValue) / (float)Items.Count());
                GetAt(i).Scaling = new Vector2(1f - (GetAt(i).RenderLevel * Math.Abs(ScaleFactor)));
                GetAt(i).Opacity = Math.Min(1f, Math.Max(0f, 1f - (GetAt(i).RenderLevel / TransparencyFactor)));
            }

            if (Background != null)
            {
                Background.Size = Size;
                Background.Position = Position;
                Background.Origin = Origin;
                Background.RenderLevel = 1f;
            }
        }
    }
}
